/*!
// Testweb v0.4.0
// http://or-change.cn
// Copyright 2014 OrChange Inc. All rights reserved.
// Licensed under the GPL License.
*/
/*jslint vars: true, forin: true, plusplus: true */
/*global define: false */
/**
 * Mechanism design of its reference computer composition.
 * It has a program counter, instruction buffer and memory
 * device. Memory is divided into the program and data segments.
 * Process inlude Steps and other attributes.
 *
 * @module Process
 * @requires OC
 * @requires Step
 * @requires Dictionary
 * @requires $
 * @requires Entity
 * @example
 *     var Process = require("./oc.process"); //How to require Process module.
 */
define(function (require, exports, module) {
	'use strict';
	var $ = require("lib/jquery/jquery");
	var OC = require("lib/core/oc");
	var Entity = require("./entity");
	var Dictionary = require("./dictionary");
	var Result = require("./result");

	var Process;
	/**
	 * Mechanism design of its reference computer composition.
	 * It has a program counter, instruction buffer and memory
	 * device. Memory is divided into the program and data segments.
	 * Process inlude Steps and other attributes.
	 *
	 * @class Process
	 * @constructor
	 * @param [counter=0] {number} The current loop is being executed step number.
	 * @param [loop=0] {number} How many times has the programe run.
	 * @param [maxLoops=DEFAULT_MAX_LOOPS] {number} The maximum number of runs of the program.
	 * @param [current_step=null] {number} The maximum number of runs of the program.
	 * @param [origin="/"] {string} The original page url of this process.
	 * @example
	 *		new Process({
	 *			name:"xxxx",
	 *			ID: "ADE3910EF8475B31",
	 *			maxLoops:30,
	 *			origin: "/xxx/xx",
	 *			clear_session: true,
	 *			back_to_origin: true
	 *		});
	 */
	Process = function Process(options) {
		/**
		 * Current loop times.
		 * @property loop
		 * @type number
		 */
		this.loop = 0;
		/**
		 * The maximum number of runs of the program.
		 * @property MAX_LOOPS
		 * @type number
		 */
		this.MAX_LOOPS = parseInt(options.MAX_LOOPS, 0) || OC.cfg("DEFAULT_MAX_LOOPS");
		/**
		 * Programe counter.
		 * @property counter
		 * @type number
		 */
		this.counter = 0;
		/**
		 * Instruction Register.
		 * @property current_step
		 * @type DelayStep|EventStep
		 */
		this.current_step = null;
		/**
		 * Programe segments. Storage all steps.
		 * @property program
		 * @type array
		 */
		this.program = [];

		/**
		 * The original page url of this process.
		 * @property origin
		 * @type string
		 */
		this.origin = options.origin || "/";

		this.config = {
			clear_session: false,
			back_to_origin: false
		};
		/**
		 * Data segments. Storage input data, data dictionary.
		 * @property data_dict
		 * @type object
		 */
		this.data_dict = options.dictionary || null;
		/**
		 * Data segments. Storage output data.
		 * @property result
		 * @type object
		 */
		this.result = null;
		Entity.call(this, options);
	};
	Process.prototype = new Entity();
	Process.prototype.constructor = Process;

	/**
	 * Controller.
	 * @method $core
	 * @private
	 */
	Process.prototype.$core = function () {
		// Continue to execute if not complete.
		if (++this.counter < this.getLength()) {
			// Buff current step object.
			this.current_step = this.program[this.counter];
			this.$execute();

		} else if (++this.loop < this.MAX_LOOPS) {
			// Reset counter for next loop. But if all
			// loops have completed, return function.
			this.$callAfterLoopEnd();

			this.current_step = this.program[this.counter = 0];
			this.$execute();
		} else {
			this.$callAfterEnd();
		}
	};
	/**
	 * Execute engine.
	 * @method $execute
	 * @private
	 */
	Process.prototype.$execute = function () {
		this.current_step.run(function (result) {
			this.result.addResult(result);
		});
	};
	/**
	 * Initialization.
	 * @method $initialize
	 * @returns {Process} this
	 * @private
	 * @chainable
	 */
	Process.prototype.$initialize = function () {
		$('iframe').attr("src", this.origin);
		this.current_step = this.program[0];
		this.result = new Result({
			name: "fdsfsf",
			length: this.program.length
		});

		return this;
	};
	/**
	 * 将循环计数器和程序计数器置为0，将指令寄存器置为null
	 *
	 * @method reset
	 * @private
	 * @chainable
	 */
	Process.prototype.$reset = function () {
		this.loop = 0;
		this.counter = 0;
		this.current_step = null;

		return this;
	};
	/**
	 * 定义一个循环结束要做的事情，可能包括重置会话和返回测试原点
	 *
	 * @method reset
	 * @private
	 * @chainable
	 */
	Process.prototype.$callAfterLoopEnd = function () {
		var key, cookies;

		this.result.addRow();

		if (this.config.clear_session) {
			cookies = $.cookie();
			for (key in cookies) {
				$.cookie(key, null, {
					path: "/"
				});
			}
		}

		if (this.config.back_to_origin) {
			$('iframe').attr("src", this.origin);
		}

		if (typeof this.after_loop === "function") {
			this.after_loop.call(this);
		}
	};
	Process.prototype.$callAfterEnd = function () {
		//TODO something after the whole process completed.
		if (typeof this.success === "function") {
			this.sucess.call(this);
		}
		return this.result;
	};
	/**
	 * 启动这个过程
	 *
	 * @method play
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.play = function () {
		this.$initialize();
		this.$execute();
		return this;
	};
	/**
	 * 暂停这个过程
	 *
	 * @method pause.
	 * @chainable
	 */
	Process.prototype.pause = function () {
		// Clear timers.
		this.current_step.abort();
		return this;
	};
	/**
	 * 停止过程并复位
	 *
	 * @method stop.
	 * @chainable
	 */
	Process.prototype.stop = function () {
		this.pause().$reset();
		return this;
	};
	/**
	 * 从暂停状态恢复运行。
	 *
	 * @method play
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.resume = function () {
		this.$execute();
		return this;
	};
	/**
	 * 复位并重新启动过程
	 *
	 * @method restartMe
	 */
	Process.prototype.restart = function () {
		this.stop().play();
		return this;
	};

	/**
	 * Add one step object to Process.program
	 * @method addStep
	 * @param {Step} step
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.addStep = function (step) {
		// Take steps to join the program. Set _process of steps
		// current process.
		this.program.push(step.setParent(this));

		return this;
	};
	/**
	 * Insert a Step to program.
	 * @method insertStep
	 * @param index {number} The position of the program queue.
	 * @param step {DelayStep|EventStep} A step need to insert.
	 * @returns {undefined}
	 * @chainable
	 */
	Process.prototype.insertStep = function (index, step) {
		// Insert a step at index of program.
		this.program.splice(index, 0, step);

		return this;
	};
	Process.prototype.replaceStep = function (index, step) {
		this.program[index] = step;

		return this;
	};
	/**
	 * Delete a Step from program.
	 * @method deleteStep
	 * @param index {number} The position of the program queue.
	 * @returns {DelayStep|EventStep} The deleted step.
	 */
	Process.prototype.deleteStep = function (index) {
		// Delete a step from program. Start from
		// index last 1.
		return this.program.splice(index, 1);
	};
	/**
	 * 将按index指定的step在program中向上移动
	 *
	 * @method moveStepUp
	 * @param index {number}
	 * @returns {Process}
	 * @chainable
	 */
	Process.prototype.moveStepUp = function (index) {
		var buff;
		if (index > 0) {
			buff = this.program[index - 1];
			this.program[index - 1] = this.program[index];
			this.program[index] = buff;
		}

		return this;
	};
	/**
	 * 将按index指定的step在program中向下移动
	 *
	 * @method moveStepUp
	 * @param index {number}
	 * @returns {Process}
	 * @chainable
	 */
	Process.prototype.moveStepDown = function (index) {
		var buff;
		if (index < this.program.length - 1) {
			buff = this.program[index - 1 + 2];
			this.program[index - 1 + 2] = this.program[index];
			this.program[index] = buff;
		}

		return this;
	};
	Process.prototype.getStep = function (index) {
		return this.program[index];
	};
	/**
	 * Add a dataDict to process by ajax.
	 * @method assignDictionary
	 * @param dictionary {Dictionary}
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.addDictionary = function (dictionary) {
		this.data_dict = dictionary;
		this.MAX_LOOPS = dictionary.getLength();

		return this;
	};

	/**
	 * Return the number of steps in the process.
	 * @method getLength
	 * @returns {number} Length of property program.
	 */
	Process.prototype.getLength = function () {
		return this.program.length;
	};
	/**
	 * Set the position of program.
	 * @method setCounter
	 * @param pos {number} The value of PC you want to set.
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.setCounter = function (pos) {
		this.counter = pos;
		return this;
	};

	/**
	 * Get the position of program.
	 * @method getCounter
	 * @returns {number} The value of PC.
	 */
	Process.prototype.getCounter = function () {
		return this.counter;
	};
	Process.prototype.hasDictionary = function () {
		return this.dictionary instanceof Dictionary;
	};

	/**
	 * Drop the whole step program.
	 * @method destroyProgram
	 * @chainable
	 */
	Process.prototype.destroyProgram = function () {
		this.program = [];
		return this;
	};


	return Process;
});